package com.zhb.cloud.core.distribute.mysql.lock;

import java.util.concurrent.TimeUnit;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;

import com.zhb.cloud.core.distribute.lock.DistributedLock;
import com.zhb.cloud.core.distribute.lock.DistributedLockException;
import com.zhb.cloud.core.distribute.lock.DistributedLockUtils;

/**
 * @ClassName: MysqlNamedDistributedLock
 * @author: zhb
 * @date: 2017年8月3日 下午7:06:21
 * @Description: mysql 分布式锁的实现
 *
 */
public class MysqlNamedDistributedLock implements DistributedLock {
	private static final Logger LOG = LoggerFactory.getLogger(MysqlNamedDistributedLock.class);
	/**
	 * 锁空间
	 */
	public static final String NAMESPACE = "/px/locks";

	/**
	 * 数据源
	 */
	private DataSource dataSource;

	/**
	 * jdbcTemplate
	 */
	private JdbcTemplate jdbcTemplate;

	/**
	 * 需要上锁的空间
	 */
	@SuppressWarnings("unused")
	private String namespace;

	/**
	 * 加锁路径
	 */
	private String lockPath;

	/**
	 * @param dataSource
	 *            the database source
	 * @param path
	 *            lock path
	 * @param namespace
	 *            lock namespace
	 * @Desc 锁定某个数据源的某个命名空间下的对应路径
	 */
	public MysqlNamedDistributedLock(DataSource dataSource, String path, String namespace) {
		initDistributedLock(dataSource, path, namespace);
	}

	/**
	 * Construct with default namespace:{@value #NAMESPACE}
	 *
	 * @param dataSource
	 *            the database source
	 * @param path
	 *            lock path
	 */
	public MysqlNamedDistributedLock(DataSource dataSource, String path) {
		this(dataSource, path, NAMESPACE);
	}

	/**
	 * @param dataSource
	 *            the database source
	 * @param path
	 *            the lock path, such as /example/lock
	 * @param namespace
	 *            lock namespace
	 */
	private void initDistributedLock(DataSource dataSource, String path, String namespace) {
		if (dataSource == null) {
			throw new DistributedLockException("No dataSource found");
		}
		DistributedLockUtils.validatePath(path);

		this.namespace = namespace;
		this.lockPath = namespace + path;
		this.dataSource = dataSource;
		this.jdbcTemplate = new JdbcTemplate(dataSource);

		LOG.info("Initialize databse lock instance, path={} dataSource={}", this.lockPath, this.dataSource);
	}

	@Override
	public void lock() throws DistributedLockException {
		tryLock(Long.MAX_VALUE, TimeUnit.SECONDS);
	}

	@Override
	public void lock(long time, TimeUnit unit) throws DistributedLockException {
		try {
			// FIXME: lock expiration
			lock();
			LOG.info("{} acquire lock: {}", Thread.currentThread(), lockPath);
		} catch (Exception e) {
			throw new DistributedLockException("failed to acquire the lock", e);
		}
	}

	@Override
	public void unlock() throws DistributedLockException {
		boolean result = false;
		try {
			Object[] args = new Object[] { this.lockPath };
			Number number = jdbcTemplate.queryForObject("SELECT COALESCE(RELEASE_LOCK(?),0)", args, Integer.class);
			// 1 success, 0 fail
			result = number != null && number.intValue() == 1;
		} catch (Exception e) {
			throw new DistributedLockException("", e);
		}
		LOG.info("{} release lock: {}, result: {}", Thread.currentThread(), lockPath, result);
	}

	@Override
	public boolean tryLock() throws DistributedLockException {
		return tryLock(0, TimeUnit.SECONDS);
	}

	@Override
	public boolean tryLock(long time, TimeUnit unit) throws DistributedLockException {
		boolean result = false;
		try {
			Object[] args = new Object[] { this.lockPath, unit.toSeconds(time) };
			Number number = jdbcTemplate.queryForObject("SELECT COALESCE(GET_LOCK(?, ?),0)", args, Integer.class);
			// 1 success, 0 fail
			result = number != null && number.intValue() == 1;
		} catch (Exception e) {
			throw new DistributedLockException("", e);
		}
		LOG.info("{} try lock: {} result: {}", Thread.currentThread(), lockPath, result);
		return result;
	}

	@Override
	public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) {
		throw new RuntimeException("method unsupported");
	}

	@Override
	public boolean isLocked() throws DistributedLockException {
		boolean result = false;
		try {
			Object[] args = new Object[] { this.lockPath };
			Number number = jdbcTemplate.queryForObject("SELECT COALESCE(IS_FREE_LOCK(?),0)", args, Integer.class);
			// 1 success, 0 fail
			result = number != null && number.intValue() == 1;
		} catch (Exception e) {
			throw new DistributedLockException("", e);
		}
		LOG.info("{} is lock: {} result: {}", Thread.currentThread(), lockPath, result);
		return result;
	}
}
